﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using WMM.Tapioca.Validate;
using WMM.Tapioca.Utilities;
using System.Xml.Linq;

namespace WMM.Tapioca.Linkbase
{
    /// <summary>
    /// Extended links are [XLINK] annotated XML fragments that document a set of relationships between resources. 
    /// XBRL extended links document relationships between resources that are XML fragments.
    /// </summary>
    public class ExtendedLink
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private string namespaceUri;

        /// <summary>
        /// The NamespaceURI of Extended Link.
        /// </summary>
        public string NamespaceUri
        {
            get { return namespaceUri; }
            set { namespaceUri = value; }
        }

        private string localName;

        /// <summary>
        /// The local name of Extended Link.
        /// </summary>
        public string LocalName
        {
            get { return localName; }
            set { localName = value; }
        }

        /// <summary>
        /// A field of Link Role that apply to this Extended Link.
        /// </summary>
        public Uri ExtendedLinkRole
        {
            get;
            set;
        }

        private string lang;

        /// <summary>
        /// A lang this Extended Link.
        /// </summary>
        public string Lang
        {
            get { return lang; }
            set { lang = value; }
        }

        ///// <summary>
        ///// A field of Unresolved Locator objects that apply to this Extended Link.
        ///// </summary>
        //protected List<Locator> thisUnresolvedLocators;

        /// <summary>
        /// A collection of <see cref="Locator"/> objects that apply to this Extended Link.
        /// </summary>
        //public List<Locator> Locators
        //{
        //    get;
        //    set;
        //}

        public IndexedList<Locator> Locators
        {
            get;
            set;
        }

        /// <summary>
        /// A collection of <see cref="Resource"/> objects that apply to this Extended Link.
        /// </summary>
        public List<Resource> Resources
        {
            get;
            set;
        }

        /// <summary>
        /// A collection of <see cref="Arc"/> objects that apply to this Extended Link.
        /// </summary>
        public List<Arc> Arcs
        {
            get;
            set;
        }

        /// <summary>
        /// The xml node containing this extended link.
        /// </summary>
        public XElement XbrlDocument
        {
            get;
            private set;
        }

        private XbrlDocument parentDocument;

        /// <summary>
        /// The parent document containing this extended link.
        /// </summary>
        public XbrlDocument ParentDocument
        {
            get { return parentDocument; }
            set { parentDocument = value; }
        }


        internal ExtendedLink(XElement extendedLinkElement, XElement xbrlRootElement, XbrlDocument document)
        {
            log.Debug("Creating ExtendedLink: " + extendedLinkElement.Name.LocalName);

            parentDocument = document;
            this.XbrlDocument = xbrlRootElement;
            localName = extendedLinkElement.Name.LocalName;
            NamespaceUri = extendedLinkElement.Name.NamespaceName;
            XNamespace ns = extendedLinkElement.GetNamespaceOfPrefix("xlink");
            string attrValue = extendedLinkElement.GetAttributeValue("role");

            XNamespace nsXml = extendedLinkElement.GetNamespaceOfPrefix("xml");
            lang = (string)extendedLinkElement.Attribute(nsXml + "lang") ?? string.Empty;

            try
            {
                this.ExtendedLinkRole = new Uri(attrValue);
            }
            catch (Exception ex)
            {
                log.Error("Validation error on Uri:", ex);
            }

            this.Locators = new IndexedList<Locator>();
            //this.Locators = new List<Locator>();
            this.Arcs = new List<Arc>();
            this.Resources = new List<Resource>();

            //thisUnresolvedLocators = new List<Locator>();

            ReadLocators(extendedLinkElement);
            ReadResources(extendedLinkElement);
            ReadArcs(extendedLinkElement);
        }


        //------------------------------------------------------------------------------------
        // Read <loc> nodes and build a Locator object for each one.
        //
        // The implementation places the new Locator object in two places: one in a list of
        // all locators and one in a list of unresolved locators. Unresolved locators are
        // kept in that list until a <presentationArc> is found. Once a <presentationArc> is
        // processed, the locator references in the <presentationArc> are resolved using the
        // locators in the unresolved locator list. Once the resolution is performed, then
        // the Locator is removed from the unresolved locator list (but kept in the list of
        // all Locator objects).
        //
        // A more simplistic implementation would be to simply keep a single list of all
        // Locator objects and resolve all locator references in presentation arcs once the
        // entire document is read. However, locator names are local to a <presentationLink>
        // and can be resued. Consider the following:
        //
        // <presentationLink xlink:type="extended" xlink:role="http://www.xbrl.org/2003/role/link">
        //      <loc xlink:type="locator" xlink:href="306-Required.xsd#flag" xlink:label="flag"/>
        //      <loc xlink:type="locator" xlink:href="306-Required.xsd#monetaryItem" xlink:label="monetaryItem"/>
        //      <presentationArc xlink:type="arc" xlink:from="flag" xlink:to="monetaryItem" xlink:arcrole="http://www.xbrl.org/2003/arcrole/requires-element"/>
        // </presentationLink>
        // <presentationLink xlink:type="extended" xlink:role="http://www.xbrl.org/2003/role/link">
        //      <loc xlink:type="locator" xlink:href="306-Required.xsd#tFlag" xlink:label="flag"/>
        //      <loc xlink:type="locator" xlink:href="306-Required.xsd#monetaryItem" xlink:label="monetaryItem"/>
        //      <presentationArc xlink:type="arc" xlink:from="flag" xlink:to="monetaryItem" xlink:arcrole="http://www.xbrl.org/2003/arcrole/requires-element"/>
        // </presentationLink>
        //
        // The locator label "flag" is used twice, for two different hrefs. Waiting until the
        // end to resolve all of the locator references into Locator objects may result in the
        // wrong locator being matched to a label. Therefore, the process is this:
        //
        // * read <loc> elements; place Locator into unresoilved list
        // * read <presentationArc>
        // * resolve "from" and "to" links to Locator objects found in unresolved list
        //
        // This also means that, after a <presentationArc> is fully processed, that the
        // unresolved Locator list is empty.
        //------------------------------------------------------------------------------------
        private void ReadLocators(XElement extendedLinkElement)
        {
            foreach (XElement currentElement in extendedLinkElement.Elements())
            {
                if (currentElement.Name.LocalName.Equals("loc"))
                {
                    Locator newLocator = new Locator(currentElement);
                    this.Locators.Add(newLocator);
                    this.Locators.AddByName(newLocator);
                    //thisUnresolvedLocators.Add(NewLocator);
                }
            }
        }


        private void ReadResources(XElement extentedLinkNode)
        {
            string label = null;

            XNamespace ns = Constants.XLINK;

            foreach (XElement currentElement in extentedLinkNode.Elements())
            {
                // label resource
                if (currentElement.Name.LocalName.Equals("label"))
                {
                    label = (string)currentElement.Attribute(ns + "label") ?? string.Empty;
                    this.Resources.Add(new Label(currentElement, label, "", this, parentDocument.Dts));
                    continue;
                }

                //reference resource
                if (currentElement.Name.LocalName.Equals("reference"))
                {
                    label = (string)currentElement.Attribute(ns + "label") ?? string.Empty; 
                    this.Resources.Add(new Reference(currentElement, label, "", this));
                    continue;
                }

                //footnote resource
                if (currentElement.Name.LocalName.Equals("footnote"))
                {
                    label = (string)currentElement.Attribute(ns + "label") ?? string.Empty; 
                    this.Resources.Add(new Footnote(currentElement, label, "", this));
                    continue;
                }

                //any resource
                XAttribute typeAttr = currentElement.Attribute(ns + "type");

                if (typeAttr != null)
                {
                    if (typeAttr.Value.Equals("resource"))
                    {
                        label = (string)currentElement.Attribute(ns + "label") ?? string.Empty;
                        this.Resources.Add(new Resource(currentElement, label, "", this));
                    }
                }
            }
        }


        private void ReadArcs(XElement extendedLinkElement)
        {
            foreach (XElement currentElement in extendedLinkElement.Elements())
            {
                XNamespace ns = Constants.XLINK;

                string type = (string)currentElement.Attribute(ns + "type") ?? string.Empty;

                if (type.Equals("arc"))
                {
                    Arc newArc = new Arc(currentElement, this);
                    this.Arcs.Add(newArc);
                    ResolveLocators(newArc);
                }
            }
        }


        private void ResolveLocators(Arc currentArc)
        {
            currentArc.FromLocator = GetLocator(currentArc.FromId);

            currentArc.ToLocator = GetLocator(currentArc.ToId);

            currentArc.ToResource = GetResource(currentArc.ToId);
        }


        private Resource GetResource(string locatorLabel)
        {
            foreach (Resource currentResource in this.Resources)
            {
                if (currentResource.Label.Equals(locatorLabel))
                {
                    return currentResource;
                }
            }

            return null;
        }


        private Locator GetLocator(string label)
        {
            return Locators.ContainsKeyByName(label.GetHashCode()) ? Locators.GetItemByName(label.GetHashCode()) : null;

            //foreach (Locator currentLocator in this.Locators)
            //{
            //    if (currentLocator.Label.Equals(label))
            //    {
            //        return currentLocator;
            //    }
            //}

            //return null;
        }

 
    }
}
